﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.proxy.items;
using snat.proxy;
using snat.proxy.sqlapi;
using snat.Exceptions;

namespace snat.model {


    /// <summary>
    /// Delegate type for message send events
    /// </summary>
    /// <param name="message">The message which was sent</param>
    /// <param name="sourceNode">The sender of the message</param>
    public delegate void messageSent(Message message, BaseNode sourceNode);
    /// <summary>
    /// Delegate type for message receive events
    /// </summary>
    /// <param name="message">the message which was received</param>
    /// <param name="sourceNode">The recipient of the message</param>
    public delegate void messageRecieved(Message message, BaseNode recipientNode);
    /// <summary>
    /// Delegate type for when a new edge is added to this node
    /// </summary>
    /// <param name="e"></param>
    /// <param name="sourceNode"></param>
    public delegate void edgeAdded(Edge e, BaseNode sourceNode);

    public abstract class BaseNode : IComparable<BaseNode> {

        /// <summary>
        /// Triggers the send event for this node (i.e. causes this node to 'send' a message)
        /// </summary>
        /// <param name="m">The message this node sends</param>
        protected internal void TriggerSend(Message m) {
            if (MessageSend != null) MessageSend(m, this);
        }
        /// <summary>
        /// Event fired when this node has sent a message
        /// </summary>
        public event messageSent MessageSend;

        /// <summary>
        /// Triggers the receive event for this node (i.e. causes this node to 'receive a message)
        /// </summary>
        /// <param name="m">the message this node receives</param>
        protected internal void TriggerRecieve(Message m) {
            if (MessageRecieve != null) MessageRecieve(m, this);
        }
        /// <summary>
        /// Event fired when this node receives a message
        /// </summary>
        public event messageRecieved MessageRecieve;

        /// <summary>
        /// Event fired when this node has an in edge added
        /// </summary>
        public event edgeAdded InEdgeAdded;
        /// <summary>
        /// Event fired when this node has an out edge added
        /// </summary>
        public event edgeAdded OutEdgeAdded;

        /// <summary>
        /// Initial degree of this node
        /// </summary>
        public readonly int InitialDegree;
        /// <summary>
        /// Initial out degree of this node
        /// </summary>
        public readonly int InitialOutDegree;
        /// <summary>
        /// Initial in degree of this node
        /// </summary>
        public readonly int InitialInDegree;

        /// <summary>
        /// Current structural out degree of this node
        /// </summary>
        public int OutDegree {
            get {
                return outEdges.Count;
            }
        }

        /// <summary>
        /// Current structural in degree of this node
        /// </summary>
        public int InDegree {
            get {
                return inEdges.Count;
            }
        }

        /// <summary>
        /// Current structural degree of this node
        /// </summary>
        public int Degree {
            get {
                return inEdges.Count + outEdges.Count;
            }
        }

        /// <summary>
        /// Current overlay degree of this node
        /// </summary>
        public abstract int OverlayInDegree { get; }
        /// <summary>
        /// Current overlay out degree of this node
        /// </summary>
        public abstract int OverlayOutDegree { get; }
        /// <summary>
        /// Current overlay degree of this node
        /// </summary>
        public abstract int OverlayDegree { get; }

        /// <summary>
        /// Count of messages this node has sent
        /// </summary>
        public int MessageSentCount { get; private set; }
        /// <summary>
        /// Count of messages this node has received
        /// </summary>
        public int MessageReceivedCount { get; private set; }

        /// <summary>
        /// inEdges is a dictionary of the id of the originator of the edge to the edge itself
        /// </summary>
        protected Dictionary<int, Edge> inEdges = new Dictionary<int, Edge>();
        /// <summary>
        /// List of structural edges where this node is a target
        /// </summary>
        public List<Edge> InEdges {
            get {
                return inEdges.Values.ToList<Edge>();
            }
        }
        /// <summary>
        /// List of overlay edges where this node is a target
        /// </summary>
        public abstract List<Edge> OverlayInEdges { get; }

        /// <summary>
        /// outEdges is a dictionary of the id of the target of the edge to the edge itself
        /// </summary>
        private Dictionary<int, Edge> outEdges = new Dictionary<int, Edge>();
        /// <summary>
        /// List of structural edges where this node is a source
        /// </summary>
        public List<Edge> OutEdges {
            get {
                return outEdges.Values.ToList<Edge>();
            }
        }
        /// <summary>
        /// List of overlay edges where this node is a source
        /// </summary>
        public abstract List<Edge> OverlayOutEdges { get; }
        
        /// <summary>
        /// List of all structural edges regarding this node
        /// </summary>
        public List<Edge> Edges {
            get {
                return InEdges.Union(OutEdges).ToList<Edge>();
            }
        }
        /// <summary>
        /// List of all overlay edges regarding this node
        /// </summary>
        public List<Edge> OverlayEdges {
            get {
                return OverlayInEdges.Union(OverlayOutEdges).ToList<Edge>();
            }
        }

        /// <summary>
        /// List of structural edges where out.source == in.target and out.target = in.source
        /// </summary>
        public List<Edge> BiDirectionalEdges {
            get {
                //return those outedges where there exists an in edge such that the out-edge destination equals the in-edge source
                return OutEdges.Where(oe => InEdges.Exists(ie => ie.Source.ID == oe.Target.ID)).ToList();
            }
        }
        /// <summary>
        /// List of overlay edges where out.source == in.target and out.target = in.source
        /// </summary>
        public List<Edge> OverlayBiDirectionalEdges {
            get {
                //return those outedges where there exists an in edge such that the out-edge destination equals the in-edge source
                return OverlayOutEdges.Where(oe => OverlayInEdges.Exists(ie => ie.Source.ID == oe.Target.ID)).ToList();
            }
        }

        /// <summary>
        /// Identifying name of this node
        /// </summary>
        public string Name { get; private set; }
        /// <summary>
        /// Network model ID number for this node
        /// </summary>
        public int ID { get; private set; }


        /// <summary>
        /// The set of all messages sent from this node throughout time
        /// </summary>
        public List<Message> MessagesSent {
            get {
                ListProxy<DBMessage> temp = MessageQuery.SentByBefore(NetworkModel.Instance.ConvertToDatabaseIDs(ID), NetworkModel.Instance.CurrentTime);
                try {
                    return temp.ToList().ConvertAll<Message>(m =>
                        new Message(m.MessageID,
                                    NetworkModel.Instance.ConvertToNetworkModelID(m.Sender.EntityID),
                                    NetworkModel.Instance.ConvertToNetworkModelIDs(m.Recipients.ConvertAll<int>(e => e.EntityID)),
                                    m.IDonNetwork,
                                    m.Restricted,
                                    m.Timestamp,
                                    m.Data));
                } catch (NullReferenceException ex) {
                    // Sender was supposed to be guarranteed to be in the network model, as this node sent all messages in list
                    throw new NodeNotFoundException("Sender EntityID was node found in the NetworkModel", ex);
                }
            }
        }
        /// <summary>
        /// The set of all messages received by this node throughout time 
        /// </summary>
        public List<Message> MessagesReceived {
            get {
                ListProxy<DBMessage> temp = MessageQuery.ReceivedByBefore(NetworkModel.Instance.ConvertToDatabaseIDs(ID), NetworkModel.Instance.CurrentTime);
                List<Message> ret = temp.ToList().ConvertAll<Message>(m =>
                    new Message(m.MessageID,
                                NetworkModel.Instance.ConvertToNetworkModelID(m.Sender.EntityID),
                                NetworkModel.Instance.ConvertToNetworkModelIDs(m.Recipients.ConvertAll<int>(e => e.EntityID)),
                                m.IDonNetwork,
                                m.Restricted,
                                m.Timestamp,
                                m.Data));
                // Remove those messages sent by nodes not in the network model
                return ret.Where(m => m.Sender != null).ToList();
            }
        }

        protected BaseNode(int _id, string _name, List<Edge> _inEdges = null, List<Edge> _outEdges = null) {
            this.Name = _name;
            this.ID = _id;

            inEdges = new Dictionary<int, Edge>();
            outEdges = new Dictionary<int, Edge>();

            if (_inEdges != null) {
                inEdges = _inEdges.ToDictionary(v => v.Source.ID, v => v);
            } else inEdges = new Dictionary<int, Edge>();
            InitialInDegree = inEdges.Count;

            if (_outEdges != null) {
                outEdges = _outEdges.ToDictionary(v => v.Target.ID, v => v);
            } else outEdges = new Dictionary<int, Edge>();
            InitialOutDegree = outEdges.Count;
            InitialDegree = InitialInDegree + InitialOutDegree;

            MessageSend += SendMessage;
            MessageRecieve += ReceiveMessage;
        }

        /// <summary>
        /// Adds an edge to this node
        /// </summary>
        /// <remarks>If the edge does not already exist, the EdgeAdded event is thrown</remarks>
        /// <param name="edge">The edge to add</param>
        public void AddEdge(Edge edge) {
            if (edge.Source.ID == ID && !outEdges.ContainsKey(NetworkModel.Instance.Nodes[edge.Target.ID].ID)) {
                outEdges.Add(NetworkModel.Instance.Nodes[edge.Target.ID].ID, edge);
                if (OutEdgeAdded != null) OutEdgeAdded(edge, this);
            } else if (edge.Target.ID == ID && !inEdges.ContainsKey(NetworkModel.Instance.Nodes[edge.Source.ID].ID)) {
                inEdges.Add(NetworkModel.Instance.Nodes[edge.Source.ID].ID, edge);
                if (InEdgeAdded != null) InEdgeAdded(edge, this);
            }
        }

        /// <summary>
        /// Updates node's data when message is sent
        /// </summary>
        /// <remarks>This is added to the MessageSend event</remarks>
        /// <param name="m">Message sent</param>
        /// <param name="n">Will be this node on event</param>
        private void SendMessage(Message m, BaseNode n) {
            MessageSentCount++;
        }

        /// <summary>
        /// Updates node's data when message is reveived
        /// </summary>
        /// <remarks>This is added to the MessageReveived event</remarks>
        /// <param name="m">Message reveived</param>
        /// <param name="n">Will be this node on event</param>
        private void ReceiveMessage(Message m, BaseNode n) {
            MessageReceivedCount++;
        }

        public int CompareTo(BaseNode other) {
            return other.ID.CompareTo(ID);
        }
    }
}
